package no.rodland.acc.service;

import javax.sql.DataSource;

import org.joda.time.DateTime;
import org.springframework.jdbc.core.RowMapper;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import no.rodland.acc.db.DbRow;
import no.rodland.acc.db.FetchService;
import no.rodland.acc.db.StoreService;
import no.rodland.acc.model.AccountGroup;
import no.rodland.acc.model.Bank;
import no.rodland.acc.model.BankAccount;
import no.rodland.acc.model.BankType;
import no.rodland.acc.model.Budget;
import no.rodland.acc.model.BudgetAccount;
import no.rodland.acc.model.BudgetAccountGroup;
import no.rodland.acc.model.RowMappers;
import no.rodland.acc.model.TextBudgetAccount;

/**
 * Created by IntelliJ IDEA.
 *
 * @Since: Jun 18, 2010 2:04:13 PM
 * @Author: fmr
 */
public class DbServiceImpl implements FetchService, StoreService {
    private int secToCache;
    private FetchServiceImpl rawService;
    private static Map<Class<? extends DbRow>, DateTime> cache = new HashMap<Class<? extends DbRow>, DateTime>();

    private Map<Integer, BankAccount> bankAccounts;
    private Map<Integer, AccountGroup> accountGroups;
    private Map<Integer, BankType> bankTypes;
    private Map<Integer, Bank> banks;
    private Map<Integer, BudgetAccount> budgetAccounts;
    private Map<Integer, BudgetAccountGroup> budgetAccountGroups;
    private Map<Integer, TextBudgetAccount> textBudgetAccounts;
    private RowMappers rowMappers;
    private StoreServiceImpl storeService;

    public DbServiceImpl(DataSource ds) {
        this(ds, 60);
    }

    public DbServiceImpl(DataSource ds, int secToCache) {
        this.rowMappers = new RowMappers(this);
        this.rawService = new FetchServiceImpl(ds, this, rowMappers);
        this.storeService = new StoreServiceImpl(ds, this, rowMappers);
        this.secToCache = secToCache;
    }

    public RowMappers getRowMappers() {
        return rowMappers;
    }

    @Override
    public Map<Integer, BankAccount> getBankAccounts() {
        if (!isValid(bankAccounts)) {
            bankAccounts = rawService.getBankAccounts();
            cache(bankAccounts);
        }
        return bankAccounts;
    }

    @Override
    public Map<Integer, Bank> getBanks() {
        if (!isValid(banks)) {
            banks = rawService.getBanks(getBankTypes(), getBankAccounts());
            cache(banks);
        }
        return banks;
    }

    @Override
    public Map<Integer, AccountGroup> getAccountGroups() {
        if (!isValid(accountGroups)) {
            accountGroups = rawService.getAccountGroups();
            cache(accountGroups);
        }
        return accountGroups;
    }

    @Override
    public Map<Integer, BankType> getBankTypes() {
        if (!isValid(bankTypes)) {
            bankTypes = rawService.getBankTypes();
            cache(bankTypes);
        }
        return bankTypes;
    }

    @Override
    public Map<Integer, BudgetAccountGroup> getBudgetAccountGroups() {
        if (!isValid(budgetAccountGroups)) {
            budgetAccountGroups = rawService.getBudgetAccountGroups(getBudgetAccounts(), getAccountGroups());
            cache(budgetAccountGroups);
        }
        return budgetAccountGroups;
    }

    @Override
    public Map<Integer, BudgetAccount> getBudgetAccounts() {
        if (!isValid(budgetAccounts)) {
            budgetAccounts = rawService.getBudgetAccounts(getAccountGroups());
            cache(budgetAccounts);
        }
        return budgetAccounts;
    }

    @Override
    public Map<Integer, TextBudgetAccount> getTextBudgetAccounts() {
        if (!isValid(textBudgetAccounts)) {
            textBudgetAccounts = rawService.getTextBudgetAccounts(getBudgetAccounts());
            cache(textBudgetAccounts);
        }
        return textBudgetAccounts;
    }

    private <T extends DbRow> boolean isValid(Map<Integer, T> rows) {
        return isValid(getClazz(rows));
    }

    private <T extends DbRow> boolean isValid(List<T> rows) {
        return isValid(getClazz(rows));
    }

    private <T extends DbRow> boolean isValid(Class<T> c) {
        if (c == null) {
            return false;
        }
        DateTime lastFetched = cache.get(c);
        if (lastFetched != null) {
            if (lastFetched.plusSeconds(secToCache).isAfter(new DateTime())) {
                return true;
            }
        }
        return false;
    }

    private <T extends DbRow> void cache(Map<Integer, T> rows) {
        cache(getClazz(rows));
    }

    private <T extends DbRow> void cache(List<T> rows) {
        cache(getClazz(rows));
    }

    private <T extends DbRow> void cache(Class<T> c) {
        if (c != null) {
            cache.put(c, new DateTime());
        }
    }

    @SuppressWarnings({"RedundantCast", "unchecked"})
    private <T extends DbRow> Class<? extends DbRow> getClazz(Map<Integer, T> rows) {
        if (rows == null || rows.size() == 0) {
            return null;
        }
        return ((T) new ArrayList(rows.values()).get(0)).getClass();
    }

    @SuppressWarnings({"RedundantCast"})
    private <T extends DbRow> Class<? extends DbRow> getClazz(List<T> rows) {
        if (rows == null || rows.size() == 0) {
            return null;
        }
        return ((T) rows.get(0)).getClass();
    }

    //
    // not cached
    //

    @Override
    public List<Budget> getBudgets(Bank bank) {
        return rawService.getBudgets(bank, getBudgetAccounts(), getBankAccounts());
    }

    @Override
    public <T extends DbRow> T getRowById(DbRow row, int id, RowMapper<T> rowMapper) {
        return rawService.getRowById(row, id, rowMapper);
    }

    @Override
    public Map<Integer, Bank> getBanks(DateTime date) {
        return rawService.getBanks(getBankTypes(), getBankAccounts(), date);
    }

    @Override
    public Bank getNextBank() {
        return rawService.getNextBank(getBankTypes(), getBankAccounts());
    }

    //
    // store stuf
    //

    @Override
    public <T extends DbRow> boolean store(T... rows) {
        return storeService.store(rows);
    }

    @Override
    public <T extends DbRow> T store(T row) {
        return storeService.store(row);
    }

    @Override
    public <T extends DbRow> int insert(List<T> rows) {
        return storeService.insert(rows);
    }

    @Override
    public boolean match(Bank bank, Budget budget) {
        return storeService.match(bank, budget);
    }
}
